/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "executor_impl.h"

#include <fstream>
#include <hdf_base.h>
#include <inttypes.h>
#include <iostream>
#include <thread>
#include <sstream>

#include "executor_impl.h"
#include "iam_logger.h"
#include "iam_para2str.h"
#include "signature_operation.h"

#define LOG_LABEL OHOS::UserIam::Common::LABEL_FACE_AUTH_IMPL

namespace OHOS {
namespace HDI {
namespace FaceAuth {
namespace {
constexpr uint16_t SENSOR_ID = 123;
constexpr uint32_t EXECUTOR_TYPE = 123;
constexpr size_t PUBLIC_KEY_LEN = 32;
constexpr int32_t DEFAULT_REMAIN_TIMES = 5;
} // namespace

std::mutex ExecutorImpl::mutex_;

ExecutorImpl::ExecutorImpl()
{
    executorInfo_ = {
        .sensorId = SENSOR_ID,
        .executorType = EXECUTOR_TYPE,
        .executorRole = ExecutorRole::ALL_IN_ONE,
        .authType = AuthType::FACE,
        .esl = ExecutorSecureLevel::ESL3,
        .publicKey = std::vector<uint8_t>(PUBLIC_KEY_LEN, 0),
        .extraInfo = {},
    };
    ResultCode ret = GenerateKeyPair();
    if (ret != ResultCode::SUCCESS) {
        IAM_LOGE("GetPublicKey fail");
        return;
    }
    ret = GetPublicKey(this->executorInfo_.publicKey);
    if (ret != ResultCode::SUCCESS) {
        IAM_LOGE("GetPublicKey fail");
        return;
    }
}

int32_t ExecutorImpl::GetExecutorInfo(ExecutorInfo &executorInfo)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    executorInfo = executorInfo_;
    IAM_LOGI("get executor information success");
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::GetTemplateInfo(uint64_t templateId, TemplateInfo &templateInfo)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    auto iter = templateIdToInfo_.find(templateId);
    if (iter == templateIdToInfo_.end()) {
        IAM_LOGE("template id not found");
        return HDF_FAILURE;
    }
    templateInfo = iter->second;
    templateInfo.lockoutDuration = iter->second.lockoutDuration;
    templateInfo.remainAttempts = iter->second.remainAttempts;
    IAM_LOGI("get template %{public}" PRId64 " information success %{public}d", templateId, templateInfo.lockoutDuration);
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::OnRegisterFinish(const std::vector<uint64_t> &templateIdList,
    const std::vector<uint8_t> &frameworkPublicKey, const std::vector<uint8_t> &extraInfo)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    static_cast<void>(extraInfo);
    static_cast<void>(frameworkPublicKey);
    for (const auto &id : templateIdList) {
        templateIdToInfo_[id] = {.executorType = EXECUTOR_TYPE, .lockoutDuration = 0, .remainAttempts = DEFAULT_REMAIN_TIMES};
    }
    IAM_LOGI("register finish");
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::Enroll(
    uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    static_cast<void>(extraInfo);
    if (callbackObj == nullptr) {
        IAM_LOGE("callbackObj is nullptr");
        return HDF_ERR_INVALID_PARAM;
    }
    IAM_LOGI("scheduleId %{public}s", GET_MASKED_STRING(scheduleId).c_str());
    int32_t ret = StartTask(true, scheduleId, GetNewTemplateId(), callbackObj);
    if (ret != ResultCode::SUCCESS) {
        IAM_LOGE("StartTask result is %{public}d", ret);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::Authenticate(uint64_t scheduleId, const std::vector<uint64_t> &templateIdList,
    const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    static_cast<void>(extraInfo);

    if (callbackObj == nullptr) {
        IAM_LOGE("callbackObj is nullptr");
        return HDF_ERR_INVALID_PARAM;
    }

    if (templateIdList.size() == 0) {
        IAM_LOGE("templateIdList size is zero");
        return HDF_FAILURE;
    }
    uint64_t templateId = templateIdList[0];
    IAM_LOGI("scheduleId %{public}s mock templateId %{public}s", GET_MASKED_STRING(scheduleId).c_str(),
        GET_MASKED_STRING(templateId).c_str());
    int32_t ret = StartTask(false, scheduleId, templateId, callbackObj);
    if (ret != ResultCode::SUCCESS) {
        IAM_LOGE("StartTask result is %{public}d", ret);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::Identify(
    uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    static_cast<void>(scheduleId);
    static_cast<void>(extraInfo);
    if (callbackObj == nullptr) {
        IAM_LOGE("callbackObj is nullptr");
        return HDF_ERR_INVALID_PARAM;
    }
    IAM_LOGI("identify, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);
    int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});
    if (ret != HDF_SUCCESS) {
        IAM_LOGE("callback result is %{public}d", ret);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::Delete(const std::vector<uint64_t> &templateIdList)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    for (const auto &id : templateIdList) {
        templateIdToInfo_.erase(id);
        IAM_LOGI("delete id %{public}" PRId64 " success", id);
    }
    IAM_LOGI("delete success");
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::Cancel(uint64_t scheduleId)
{
    IAM_LOGI("interface mock start, id %{public}" PRId64, scheduleId);
    std::lock_guard<std::mutex> guard(mutex_);
    if (!running_) {
        IAM_LOGE("no task is running");
        return HDF_FAILURE;
    }
    if (runningScheduleId_ != scheduleId) {
        IAM_LOGE("schedule id not match");
        return HDF_FAILURE;
    }
    promise_.set_value();
    IAM_LOGI("cancel success");
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::SendCommand(
    int32_t commandId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    static_cast<void>(extraInfo);
    if (callbackObj == nullptr) {
        IAM_LOGE("callbackObj is nullptr");
        return HDF_ERR_INVALID_PARAM;
    }
    std::stringstream ss;
    ss << "sendCmd msg";
    for (auto num : extraInfo) {
        ss << " 0x" << (uint32_t)num;
    }
    IAM_LOGE("%{public}s ", ss.str().c_str());
    int i = 0;
    for (i = 0; i < (int)extraInfo.size(); i++) {
        if (extraInfo[i] == 183) {
            break;
        }
    }
    IAM_LOGE("tianshi msg content %{public}d ",extraInfo[i + 8]);
    commandId = extraInfo[i + 8] - 2;
    int32_t ret;
    switch (commandId) {
        case CommandId::LOCK_TEMPLATE:
            IAM_LOGI("lock template, result is %{public}d", ResultCode::SUCCESS);
            for (const auto &pair : templateIdToInfo_) {
                templateIdToInfo_[pair.first].lockoutDuration = INT32_MAX;
                templateIdToInfo_[pair.first].remainAttempts = 0;
                IAM_LOGI("lock template id %{public}" PRId64 " success", pair.first);
            }
            ret = callbackObj->OnResult(ResultCode::SUCCESS, {});
            if (ret != HDF_SUCCESS) {
                IAM_LOGE("callback result is %{public}d", ret);
                return HDF_FAILURE;
            }
            break;
        case CommandId::UNLOCK_TEMPLATE:
            IAM_LOGI("unlock template, result is %{public}d", ResultCode::SUCCESS);
            for (const auto &pair : templateIdToInfo_) {
                templateIdToInfo_[pair.first].lockoutDuration = 0;
                templateIdToInfo_[pair.first].remainAttempts = DEFAULT_REMAIN_TIMES;
                IAM_LOGI("unlock template id %{public}" PRId64 " success", pair.first);
            }
            ret = callbackObj->OnResult(ResultCode::SUCCESS, {});
            if (ret != HDF_SUCCESS) {
                IAM_LOGE("callback result is %{public}d", ret);
                return HDF_FAILURE;
            }
            break;
        default:
            IAM_LOGD("not support CommandId : %{public}d", commandId);
            ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});
            if (ret != HDF_SUCCESS) {
                IAM_LOGE("callback result is %{public}d", ret);
                return HDF_FAILURE;
            }
    }
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::SetBufferProducer(const sptr<BufferProducerSequenceable> &bufferProducer)
{
    IAM_LOGI("interface mock start set buffer producer %{public}s",
        UserIam::Common::GetPointerNullStateString(bufferProducer.GetRefPtr()).c_str());
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::GetProperty(
    const std::vector<uint64_t> &templateIdList, const std::vector<GetPropertyType> &propertyTypes, Property &property)
{
    IAM_LOGI("interface mock start");
    std::lock_guard<std::mutex> guard(mutex_);
    for (const auto &type : propertyTypes) {
        IAM_LOGI("receive property type %{public}d", type);
    }
    property.authSubType = 1;
    property.remainAttempts = 0;
    for (const auto &id : templateIdList) {
        if (templateIdToInfo_.find(id) == templateIdToInfo_.end()) {
            IAM_LOGE("%llu id not found", id);
            return HDF_FAILURE;
        }
        if (templateIdToInfo_[id].remainAttempts > property.remainAttempts) {
            property.remainAttempts = templateIdToInfo_[id].remainAttempts;
        }
    }

    if (property.remainAttempts == 0) {
        property.lockoutDuration = INT32_MAX;
    } else {
        property.lockoutDuration = 0;
    }
    property.enrollmentProgress = "FaceEnrollmentProgress";
    property.sensorInfo = "FaceSensorInfo";

    IAM_LOGI("get property success");
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::SetCachedTemplates(const std::vector<uint64_t> &templateIdList)
{
    IAM_LOGI("interface mock start");
    IAM_LOGI("set cached templates len %{public}uz success", templateIdList.size());
    if (templateIdList.size() != 0) {
        IAM_LOGI("template is %{public}llu", templateIdList[0]);
    }
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj)
{
    IAM_LOGI("interface mock start");
    saCommandCallback_ = callbackObj;
    IAM_LOGI("register sa command callback success");
    return HDF_SUCCESS;
}

int32_t ExecutorImpl::StartTask(
    bool isEnroll, uint64_t scheduleId, uint64_t templateId, const sptr<IExecutorCallback> &callbackObj)
{
    if (running_) {
        IAM_LOGI("another task is running busy");
        callbackObj->OnResult(ResultCode::BUSY, {});
        return HDF_SUCCESS;
    }
    running_ = true;
    IAM_LOGI("add task %{public}s", GET_MASKED_STRING(runningScheduleId_).c_str());
    runningScheduleId_ = scheduleId;
    promise_ = std::promise<void>();
    future_ = promise_.get_future();
    std::thread task([this, isEnroll, templateId, callbackObj]() {
        SaCommand beginCommand = { SaCommandId::BEGIN_SCREEN_BRIGHTNESS_INCREASE };
        SaCommand endCommand = { SaCommandId::END_SCREEN_BRIGHTNESS_INCREASE };
        IAM_LOGI("task begin execute %{public}s", GET_MASKED_STRING(runningScheduleId_).c_str());
        if (templateIdToInfo_.find(templateId) != templateIdToInfo_.end() &&
            templateIdToInfo_[templateId].remainAttempts == 0) {
            IAM_LOGE("template id %{public}" PRId64 " is locked", templateId);
            int32_t ret2 = callbackObj->OnResult(ResultCode::LOCKED, {});
            if (ret2 != HDF_SUCCESS) {
                IAM_LOGE("failed to send OnResult");
            }
            {
                std::lock_guard<std::mutex> guard(mutex_);
                runningScheduleId_ = 0;
                running_ = false;
            }
            return;
        }
        saCommandCallback_->OnSaCommands(std::vector<SaCommand> {beginCommand});
        const std::chrono::seconds maxWaitTime(1);
        ResultCode callbackResult = ResultCode::CANCELED;
        auto ret = future_.wait_for(maxWaitTime);
        if (ret == std::future_status::ready) {
            IAM_LOGE("future ready, task canceled");
            callbackResult = ResultCode::CANCELED;
        } else {
            IAM_LOGE("future timeout, task finish");
            callbackResult = ReadStubTaskResult();
        }
        saCommandCallback_->OnSaCommands(std::vector<SaCommand> {endCommand});
        std::vector<uint8_t> resultTlv;
        if (callbackResult == ResultCode::SUCCESS) {
            IAM_LOGI("tianshi 251 %{public}s", GET_MASKED_STRING(runningScheduleId_).c_str());
            if (isEnroll) {
                this->ProcEnrollSuccess(templateId);
            } else {
                this->ProcAuthSuccess(templateId);
            }
        } else if (callbackResult == ResultCode::FAIL) {
            if (!isEnroll) {
                this->ProcAuthFail(templateId);
            }
        }
        if (templateIdToInfo_.find(templateId) != templateIdToInfo_.end()) {
            ResultCode ret1 = GetResultTlv(
                callbackResult, runningScheduleId_, this->executorInfo_.executorType, templateId,  templateIdToInfo_[templateId].remainAttempts, resultTlv);
            if (ret1 != ResultCode::SUCCESS) {
                IAM_LOGE("GetResultTlv fail");
            }
        }
        int32_t ret2 = callbackObj->OnResult(callbackResult, resultTlv);
        if (ret2 != HDF_SUCCESS) {
            IAM_LOGE("failed to send OnResult");
        }
        {
            std::lock_guard<std::mutex> guard(mutex_);
            runningScheduleId_ = 0;
            running_ = false;
        }
        IAM_LOGI("task end execute");
    });
    task.detach();
    return HDF_SUCCESS;
}

ResultCode ExecutorImpl::ReadStubTaskResult()
{
    IAM_LOGI("begin ReadStubTaskResult 1");
    std::ifstream stubFile("/data/service/el1/public/pinauth/auth_result.dat");
    if (!stubFile.is_open()) {
        IAM_LOGI("failed to open stub file 1");
        return ResultCode::GENERAL_ERROR;
    }

    int32_t result;
    stubFile >> result;
    if (stubFile.fail()) {
        IAM_LOGI("failed to read result from stub file");
        return ResultCode::GENERAL_ERROR;
    }
    IAM_LOGI("end ReadStubTaskResult %{public}d", result);
    return static_cast<ResultCode>(result);
}

void ExecutorImpl::ProcAuthSuccess(uint64_t templateId)
{
    IAM_LOGI("start");
    std::lock_guard<std::mutex> guard(mutex_);
    auto iter = templateIdToInfo_.find(templateId);
    if (iter == templateIdToInfo_.end()) {
        IAM_LOGE("template id %{public}" PRId64 "not found", templateId);
        return;
    }
    iter->second.remainAttempts = DEFAULT_REMAIN_TIMES;
    IAM_LOGI("template %{public}" PRId64 " reset remain times", templateId);
    return;
}
void ExecutorImpl::ProcAuthFail(uint64_t templateId)
{
    IAM_LOGI("start");
    std::lock_guard<std::mutex> guard(mutex_);
    auto iter = templateIdToInfo_.find(templateId);
    if (iter == templateIdToInfo_.end()) {
        IAM_LOGE("template id %{public}" PRId64 "not found", templateId);
        return;
    }

    if (iter->second.remainAttempts > 0) {
        iter->second.remainAttempts--;
    }
    IAM_LOGI(
        "template %{public}" PRId64 " proc auth fail, remain times %{public}d", templateId, iter->second.remainAttempts);
    return;
}

void ExecutorImpl::ProcEnrollSuccess(uint64_t templateId)
{
    IAM_LOGI("start");
    templateIdToInfo_[templateId] = {.executorType = EXECUTOR_TYPE, .remainAttempts = DEFAULT_REMAIN_TIMES};
    IAM_LOGI("add template %{public}" PRId64, templateId);
}

uint64_t ExecutorImpl::GetNewTemplateId()
{
    for (uint64_t id = 0; id < 1000; id++) {
        if (templateIdToInfo_.find(id) == templateIdToInfo_.end()) {
            return id;
        }
    }
    return 0;
}
} // namespace FaceAuth
} // namespace HDI
} // namespace OHOS
